<!--
  - 短时傅里叶变换 STFT
  - 
 -->

<!-- 视图层 --><!-- 页头(上) -->
<template><header-comp />

<!-- 功能简介 -->
<uni-card title="功能简介">

  <!-- 文字 -->
  <view><text>
  　　短时傅里叶变换（Short-Time Fourier Transform, STFT）。\n
  　　直接用的TF.js的API。
  </text></view>

</uni-card>

<!-- TFjs初始化组件 -->
<tfjsInit />

<!-- 操作 -->
<uni-card title="操作">
  <!-- 通告栏 -->
  <uni-notice-bar text=
    "相关参数细节看代码逻辑层。"
  />

  <!-- 训练按钮容器及按钮 -->
  <view class="center"><button
    :disabled="!tfBackendRef"
    @click="testMain()"
    size="mini" type="primary"
  >
    运行代码
  </button></view>

  <!-- 打开图表板容器及按钮 -->
  <view class="center"><button
    @click="showVisor()"
    size="mini" type="primary"
  >
    打开图表板
  </button></view>

</uni-card>

<!-- 页脚(下) -->
<footer-comp /></template>

<!-- 逻辑层 -->
<script setup>
// 导入vue的onMounted、ref方法
// import { shallowRef, ref } from "vue"
// 导入tfjs-visor库
import * as tf from "@tensorflow/tfjs"
import * as tfvis from "@tensorflow/tfjs-vis"
// 导入TF初始化组件
import tfjsInit from "../index/tfjs-init.vue"
// 导入xlsx库
// import * as XLSX from "xlsx"
// 导入公共方法库的方法
import {
  // TF初始化的确认flag
  tfBackendRef,
  // 打开图表板
  showVisor,
  // AOA数组生成线形图
  aoaTotfvisChart,
  // 数据归一化方法
  minToZero, maxToOne,
} from "../../scripts/app_common.js"
// 导入测试数据集
import { testData } from "../../datasets/test-data.js"


/**
 * @ref对象
 */


/**
 * @其他对象
 */

/**
 * @主方法
 * 实际执行的方法
 */
function testMain() {

  // 记个时
  console.time("stft处理耗时")
  console.log("开始对数据进行stft处理...")

  /**
   * @数据预处理
   */
  // 归一化
  minToZero(testData.y, true)
  maxToOne(testData.y)
  // 数据先做个图
  aoaTotfvisChart(
    // 图表类型
    "linechart",
    // 图表容器
    { tab: "数据", name: "原始谱图" },
    // 图表数据：第一个数据
    [testData],
    // 数据序列标签
    ["数据"]
  )

  /**
   * @STFT的遍历
   */
  // stft的最大帧长
  const stftMaxLength = 300
  // 窗口长度的遍历步长，影响生成图像数量
  const frameLengthStep = 50
  // 步长
  const frameStep = 1
  // FFT的计算选点长度
  const fftLength = null
  // 遍历testData的y
  for (let frameLength = 3; frameLength < stftMaxLength; frameLength += frameLengthStep) {
    // tidy叠甲，及时回收内存
    tf.tidy(() => {
      
      /**
       * @边缘处理
       * 往数据数组左右补0，确保输出数组的长度和输入数据一致
       */
      // 深拷贝testData对象的y
      const yDeepCopy = JSON.parse(JSON.stringify(testData.y))
      // 边缘填充数，向下取整
      const padding = Math.floor(frameLength / 2)
      // 建立边缘填充数组，用0填充
      const paddingArr = new Array(padding).fill(0)
      // 在yDeepCopy的头部和尾部分别添加paddingArr
      yDeepCopy.unshift(...paddingArr)
      yDeepCopy.push(...paddingArr)
      
      /**
       * @短时傅立叶变换STFT
       * 目前是合并实部虚部取绝对值
       */
      // 用处理好的数组建立一维张量
      const dataTensor = tf.tensor1d(yDeepCopy)
      // 短时傅立叶变换，然后合并实部虚部取绝对值
      const stftTensor = tf.signal.stft(
        // 数据
        dataTensor,
        // 窗口大小
        frameLength,
        // 步长
        frameStep,
        // FFT长度，默认为null，自动选择
        fftLength,
        // 窗口函数，默认为汉宁窗
        tf.signal.hannWindow
      // 链式调用，取绝对值（合并实部和虚部）
      ).abs()

      // // 归一化
      // const tensorMin = stftTensor.min()
      // const tensorMax = stftTensor.max()
      // const normalization = stftTensor.sub(tensorMin)
      //   .div(tensorMax.sub(tensorMin))

      // 控制台打印生成的数组
      tf.print(stftTensor, true)
      
      /**
       * @作图
       * 热图
       */
      // 直接作图看看
      tfvis.render.heatmap(
        // 图表容器
        { tab: "数据", name: `窗口宽度：${ frameLength }` },
        // 数据
        { values: stftTensor }
      )
      
    })
  }

  // 输出耗时
  console.timeEnd("stft处理耗时")

}

</script>
